﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using HeapSpecSupport;
using SimpleIR;

namespace Evaluator.EvalState
{
	/// <summary>
	/// This class is used to represent class values (just maps of fields to types).
	/// </summary>
	internal abstract class EClass : EValue
	{
		/// <summary>
		/// An array of entries for each of the fields that are defined in the structure.
		/// </summary>
		private readonly FieldValueEntry[] m_fieldinfo;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_fieldinfo != null);
		}

		protected EClass(IRClassType tt, FieldValueEntry[] fvalues)
			: base(tt)
		{
			Contract.Requires(fvalues != null);

			this.m_fieldinfo = fvalues;
		}

		/// <summary>
		/// Subclasses may directly access the field value array.
		/// </summary>
		protected FieldValueEntry[] FieldValueArray
		{ get { return this.m_fieldinfo; } }

		/// <summary>
		/// Return the value for a given field and mark a read as occouring.
		/// </summary>
		internal EValue LoadValueFromField(IRMemberField f, long cetime)
		{
			Contract.Requires(f != null);
			Contract.Ensures(Contract.Result<EValue>() != null);

			Contract.Assert(Array.Exists(this.FieldValueArray, (FieldValueEntry fev) => fev.Field == f));

			int pos = Array.FindIndex(this.m_fieldinfo, (FieldValueEntry fev) => fev.Field == f);
			this.m_fieldinfo[pos] = this.m_fieldinfo[pos].ComputeNewEntryForReadFromField(cetime);

			return this.m_fieldinfo[pos].Value;
		}

		/// <summary>
		/// Set the value for a given field and mark a write as occouring.
		/// </summary>
		internal void StoreValueToField(IRMemberField f, EValue newval, long cetime)
		{
			Contract.Requires(f != null);
			Contract.Requires(IRProgram.IsT1AssignableToT2(newval.ValueType, f.FieldType));

			Contract.Assert(Array.Exists(this.FieldValueArray, (FieldValueEntry fev) => fev.Field == f));

			int pos = Array.FindIndex(this.m_fieldinfo, (FieldValueEntry fev) => fev.Field == f);
			this.m_fieldinfo[pos] = this.m_fieldinfo[pos].ComputeNewEntryForWriteToField(newval, cetime);
		}
	}

	/// <summary>
	/// This class is used to represent user defined class values (just maps of fields to types).
	/// </summary>
	internal sealed class EUserDefClass : EClass
	{
		/// <summary>
		/// This constructor takes ownership of the <c>fvalues</c> array.
		/// </summary>
		private EUserDefClass(IRClassType tt, FieldValueEntry[] fvalues)
			: base(tt, fvalues)
		{ ;}

		/// <summary>
		/// Allocate and set all the fields to the default values for a struct value.
		/// </summary>
		internal EUserDefClass CreateFreshClassValueForType(IRClassType tt, long cetime)
		{
			Contract.Requires(tt != null);
			Contract.Ensures(Contract.Result<EUserDefClass>() != null);

			FieldValueEntry[] fve = new FieldValueEntry[tt.TypeDescription.AllVisibleFields.Count];

			int i = 0;
			foreach(IRMemberField mf in tt.TypeDescription.AllVisibleFields)
			{
				fve[i] = FieldValueEntry.InitializeFieldAtAllocation(mf, cetime);
				++i;
			}

			return new EUserDefClass(tt, fve);
		}

		internal override EValue ByValueCopy(long cetime)
		{
			FieldValueEntry[] fve = new FieldValueEntry[this.FieldValueArray.Length];

			for(int i = 0; i < this.FieldValueArray.Length; ++i)
				fve[i] = this.FieldValueArray[i].ComputeNewEntryForFieldCopyByValue(cetime);

			return new EUserDefClass((IRClassType)this.ValueType, fve);
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder("{" + this.ValueType.ToString() + ": ");
			for(int i = 0; i < this.FieldValueArray.Length; ++i)
			{
				if(i != 0)
					sb.Append(", ");
				sb.Append(this.FieldValueArray[i].ToString());
			}
			sb.Append("}");

			return sb.ToString();
		}
	}

	/// <summary>
	/// This class is used to represent the special array class object values.
	/// </summary>
	internal sealed class EArrayClass : EClass
	{
		private readonly EValue[] m_arraycontents;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_arraycontents != null);
		}

		/// <summary>
		/// This constructor takes ownership of the <c>fvalues</c> array and <c>acontents</c> array.
		/// </summary>
		private EArrayClass(IRContainerType tt, FieldValueEntry[] fvalues, EValue[] acontents)
			: base(tt, fvalues)
		{
			this.m_arraycontents = acontents;
		}

		/// <summary>
		/// Allocate and set all the fields to the default values for an array type value.
		/// </summary>
		internal EArrayClass CreateFreshArrayValueForType(IRContainerType tt, int length, long cetime)
		{
			Contract.Requires(tt != null && tt.IsArrayContainer);
			Contract.Ensures(Contract.Result<EArrayClass>() != null);

			FieldValueEntry[] fve = new FieldValueEntry[tt.TypeDescription.AllVisibleFields.Count];

			int i = 0;
			foreach(IRMemberField mf in tt.TypeDescription.AllVisibleFields)
			{
				fve[i] = FieldValueEntry.InitializeFieldAtAllocation(mf, cetime);
				++i;
			}

			EValue[] acontents = new EValue[length];
			
			for(int j = 0; j < length; ++j)
				acontents[j] = EValue.GenerateDefaultValueForType(tt.ContainerContentsType);

			EArrayClass reta = new EArrayClass(tt, fve, acontents);

			EPrimitive plength = new EIntegral((IRPrimitiveType)tt.CollectionCardinalityField.FieldType, length);
			reta.StoreValueToField(tt.CollectionCardinalityField, plength, cetime);

			return reta;
		}

		internal override EValue ByValueCopy(long cetime)
		{
			FieldValueEntry[] fve = new FieldValueEntry[this.FieldValueArray.Length];

			for(int i = 0; i < this.FieldValueArray.Length; ++i)
				fve[i] = this.FieldValueArray[i].ComputeNewEntryForFieldCopyByValue(cetime);

			EValue[] acontents = new EValue[this.m_arraycontents.Length];

			for(int j = 0; j < this.m_arraycontents.Length; ++j)
				acontents[j] = this.m_arraycontents[j].ByValueCopy(cetime);

			return new EArrayClass((IRContainerType)this.ValueType, fve, acontents);
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder("[" + this.ValueType.ToString() + ": ");
			for(int i = 0; i < this.m_arraycontents.Length; ++i)
			{
				if(i != 0)
					sb.Append(", ");
				sb.Append(i.ToString() + "=" + this.m_arraycontents[i].ToString());
			}
			sb.Append("]");

			return sb.ToString();
		}

		/// <summary>
		/// Return the value for a given index and mark a read as occouring.
		/// </summary>
		internal EValue LoadValueFromIndex(EIntegral index, long cetime)
		{
			Contract.Requires(index != null);
			Contract.Ensures(Contract.Result<EValue>() != null);

			if(index.IntegralValue < 0 || index.IntegralValue >= this.m_arraycontents.Length)
				DebugReport.ReportError("Array out of bounds.");

			//hit fields to get use/mod right
			this.LoadValueFromField(((IRContainerType)this.ValueType).CollectionContentsField, cetime);
			this.LoadValueFromField(((IRContainerType)this.ValueType).CollectionCardinalityField, cetime);

			return this.m_arraycontents[index.IntegralValue];
		}

		/// <summary>
		/// Set the value to a given array index and mark a write as occouring.
		/// </summary>
		internal void StoreValueToField(EIntegral index, EValue newval, long cetime)
		{
			Contract.Requires(index != null);
			Contract.Requires(IRProgram.IsT1AssignableToT2(newval.ValueType, ((IRContainerType)this.ValueType).ContainerContentsType));

			if(index.IntegralValue < 0 || index.IntegralValue >= this.m_arraycontents.Length)
				DebugReport.ReportError("Array out of bounds.");

			//hit fields to get use/mod right
			IRContainerType ctype = ((IRContainerType)this.ValueType);
			this.LoadValueFromField(ctype.CollectionCardinalityField, cetime);
			this.StoreValueToField(ctype.CollectionContentsField, EValue.GenerateDefaultValueForType(ctype.ContainerContentsType), cetime);

			this.m_arraycontents[index.IntegralValue] = newval;
		}

		/// <summary>
		/// Return the value for the length of the array.
		/// </summary>
		internal EPrimitive LoadLengthValue(long cetime)
		{
			Contract.Ensures(Contract.Result<EValue>() != null);

			return (EPrimitive)this.LoadValueFromField(((IRContainerType)this.ValueType).CollectionCardinalityField, cetime);
		}
	}

	/// <summary>
	/// This class is used to represent the special builtin class object values (which may have special operations associated with them).
	/// </summary>
	internal abstract class EBuiltinDefClass : EClass
	{
		private readonly object m_biobj;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_biobj != null);
		}

		/// <summary>
		/// This constructor takes ownership of the <c>fvalues</c> array and <c>acontents</c> array.
		/// </summary>
		private EBuiltinDefClass(IRClassType tt, FieldValueEntry[] fvalues, object biobj)
			: base(tt, fvalues)
		{
			Contract.Requires(tt.TypeDescription.IsBuiltinType);
			Contract.Requires(biobj != null);

			this.m_biobj = biobj;
		}

		/// <summary>
		/// Allocate and set all the fields to the default values for a builtin type value.
		/// </summary>
		internal EBuiltinDefClass CreateFreshBuiltinValueForType(IRClassType tt, object biobj, long cetime)
		{
			Contract.Requires(tt != null && tt.TypeDescription.IsBuiltinType);
			Contract.Ensures(Contract.Result<EBuiltinDefClass>() != null);

			FieldValueEntry[] fve = new FieldValueEntry[tt.TypeDescription.AllVisibleFields.Count];

			int i = 0;
			foreach(IRMemberField mf in tt.TypeDescription.AllVisibleFields)
			{
				fve[i] = FieldValueEntry.InitializeFieldAtAllocation(mf, cetime);
				++i;
			}

			return new EBuiltinDefClass(tt, fve, biobj);
		}

		/// <summary>
		/// Return the builtin object representation.
		/// </summary>
		internal object BuiltinObject
		{
			get
			{
				Contract.Ensures(Contract.Result<object>() != null);

				return this.m_biobj;
			}
		}
	}
}
